﻿/*
 * Hyphenator.as - for Flash Player 9
 * Copyright (C) 2011 David Moldenhauer (dave at multiflaviour dot com)
 * Project and Source hosted on http://code.google.com/p/hyphenator-flash/
 * 
 * This ActionScript code is free software: you can redistribute it 
 * and/or modify it under the terms of the GNU Lesser General Public 
 * License (GNU LGPL) as published by the Free Software Foundation, 
 * either version 3 of the License, or (at your option) any later version.
 * 
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * Hyphenator.as is a partial port of the javascript library 
 * Hyphenator.js by Mathias Nater (mathias at mnn dot ch), which is
 * also released under GNU LGPL.
 *
 * Hyphenator X.Y.Z - client side hyphenation for webbrowsers
 * Copyright (C) 2010  Mathias Nater, Zürich (mathias at mnn dot ch)
 * Project and Source hosted on http://code.google.com/p/hyphenator/
 */

package  {
	import flash.text.TextField;
	import flash.utils.getTimer;
	
	public class Hyphenator {
		
		private static var instance:Hyphenator;
		
		public var cachedTextField:Array = new Array();
		public var indexTextField:int = 0;
		
		public var time:Number = 0;//getTimer();
		public var hyphen:String = String.fromCharCode(0x2D);
		public var nonBreakHyphen:String = String.fromCharCode(0x2011);
		public var softHyphen:String = String.fromCharCode(0xAD);
		public var enableCache:Boolean = true;
		public var exceptions:Object = new Object();
		private var languages:Object = new Object();
		private var min:int = 6;
		private var url:String = '(\\w*:\/\/)?((\\w*:)?(\\w*)@)?((([\\d]{1,3}\\.){3}([\\d]{1,3}))|(([\\w]*\\.)+([\\w]{2,4})))(:\\d*)?(\/[\\w#!:\\.?\\+=&%@!\\-]*)*';
		private var mail:String = '[\\w-\\.]+@[\\w\\.]+';
		private var lastCharRegExp:RegExp = /[^a-zA-Z0-9]/g;
		private var urlOrMailRE:RegExp = new RegExp('(' + url + ')|(' + mail + ')', 'i');
		private var urlhyphen:String = String.fromCharCode(8203);

		public static function set minWordLength(val:int):void{
			hyphenator.min = val;
		}
		public static function get minWordLength():int{
			return hyphenator.min;
		}
		
		public static function set language(obj:Object):void{
			hyphenator.languages = obj;
		}
		public static function get language():Object{
			return hyphenator.languages;
		}
		
		private static function get hyphenator():Hyphenator{
			if (instance == null) {
				instance = new Hyphenator(new SingletonEnforcer());
			}
			return instance;
		}
		
		public function Hyphenator(key:SingletonEnforcer):void {
			if(key == null){
				throw new Error("Initialization failed: the Constructor should not be called");
			}
		}
		
		public static function hyphenateTextField(target:TextField, lang:String, cache:Boolean = false):void{
			//hyphenator.time = getTimer();
			for(var i:int = 0; i < hyphenator.cachedTextField.length; i++){
				if((target === hyphenator.cachedTextField[i][0])&&(lang == hyphenator.cachedTextField[i][2])){
					hyphenator.updateTextField(hyphenator.cachedTextField[i][0], hyphenator.cachedTextField[i][1]);
					return;
				}
			}
			hyphenator.cachedTextField.push([target, hyphenator.seperateTags((target.htmlText), lang), lang]);
			hyphenator.updateTextField(target, hyphenator.cachedTextField[hyphenator.cachedTextField.length-1][1]);
		}
		
		private function updateTextField(target:TextField, text:Array):void{
			var lastIndex:int = 0;
			var lastTextIndex:int = 0;
			var currentIndex:int = 0;
			var currentText:String = "";
			target.htmlText = "";
			for(var i:int = 0; i < text[0].length; i++){
				if(text[0][i] == ""){
					if(text[1][i] != ""){currentText += text[1][i];}
					continue;
				}
				var hyphenParts:Array = text[0][i].split(hyphen);
				for(var m:int = 0; m < hyphenParts.length; m++)
				{
					var spaceParts:Array = hyphenParts[m].split(" ");
					for(var sp:int = 0; sp < spaceParts.length; sp++){
						currentText += spaceParts[sp];
						if(currentText.length != spaceParts[sp].length){
							if(sp != spaceParts.length-1){
								target.htmlText = currentText + " ";
							}else{
								target.htmlText = currentText;
							}
							if((sp == 0)&&(0 != m)){
								if(target.getLineIndexOfChar(target.text.length-2) == target.getLineIndexOfChar(lastTextIndex-1)){
									currentText = currentText.substring(0, lastIndex) + currentText.substring(lastIndex+1);
								}else{
									if(lastCharRegExp.test(currentText.charAt(lastIndex-1))){
										currentText = currentText.substring(0, lastIndex) + currentText.substring(lastIndex+1);
									}
								}
							}
						}
						if(sp != spaceParts.length-1){
							currentText += " ";
						}
					}
					lastIndex = currentText.length;
					if(hyphenParts.length-1 != m){
						currentText += hyphen;
					}
					lastTextIndex = target.text.length;
					//target.htmlText = currentText;
				}
				if(text[1][i] != ""){currentText += text[1][i]};
				target.htmlText = currentText;
			}
			//target.appendText(" -> " + String(getTimer()-time) + " ms");
		}
		
		private function seperateTags(txt:String, lang:String):Array
		{
			var returnVal:Array = new Array();
			var text:Array = new Array();
			var tags:Array = new Array();
			var index:int = -1;
			var index2:int = -1;
			var index3:int = 0;
			while((index = txt.indexOf("<", index + 1)) != -1)
			{
				index2 = txt.indexOf(">", index + 1);
				text.push(hyphenate(txt.substring(index3, index), lang));
				tags.push(txt.substring(index, index2 + 1));
				index3 = index2+1;
			}if(index3 < txt.length){
				text.push(hyphenate(txt.substring(index3), lang));
				tags.push("");
			}
			returnVal.push(text, tags);
			return returnVal;
		}
		
		public static function hyphenate(target:String, lang:String):String{
			if(!hyphenator.languages[lang].prepared){
				hyphenator.prepareLanguagesObj(lang);
			}
			if((target is String)){
				return target.replace(hyphenator.languages[lang].genRegExp, hyphenating);
			}
			function hyphenating():String{
				for(var str:String in arguments){
					if(hyphenator.urlOrMailRE.test(arguments[str])){
						return hyphenator.hyphenateURL(arguments[str]);
					}else{
						return hyphenator.hyphenateWord(lang, arguments[str]);
					}
				}
				throw new Error('expeced a string');
				return null;
			}
			return null;
		}
		
		private function prepareLanguagesObj(lang:String):void{
			var lo:Object = languages[lang];
			var wrd:*;
			if(!lo.prepared){
				if(enableCache){
					lo.cache = new Object();
				}
				if(lo.hasOwnProperty('exceptions')){
					addExceptions(lang, lo.exceptions);
					delete lo.exceptions;
				}
				if (exceptions.hasOwnProperty('global')) {
					if (exceptions.hasOwnProperty(lang)) {
						exceptions[lang] += ', ' + exceptions.global;
					} else {
						exceptions[lang] = exceptions.global;
					}
				}
				if (exceptions.hasOwnProperty(lang)) {
					lo.exceptions = convertExceptionsToObject(exceptions[lang]);
					delete exceptions[lang];
				} else {
					lo.exceptions = new Object();
				}
				convertPatterns(lang);
				wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + '-]{' + min + ',}';
				lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi');
				lo.prepared = true;
			}
		}
		
		private function convertPatterns(lang:String):void{
			var anfang:int;
			var key:String;
			var tmp:Object = new Object();
			var pats:Object = languages[lang].patterns;
			var pat:String;
			for (var plen:String in pats){
				if(pats.hasOwnProperty(plen)){
					var plength:int = parseInt(plen, 10);
					anfang = 0;
					while((pat = pats[plen].substr(anfang, plength))){
						key = pat.replace(/\d/g, '');
						tmp[key] = pat;
						anfang += plength;
					}
				}
			}
			languages[lang].patterns = tmp;
			languages[lang].patternsConverted = true;
		}
		
		public static function addExceptions(lang:String, words:String):void{
			if(lang === ''){
				lang = 'global';
			}
			if(hyphenator.exceptions.hasOwnProperty(lang)){
				hyphenator.exceptions[lang] += ", " + words;
			}else{
				hyphenator.exceptions[lang] = words;
			}
		}
		
		private function convertExceptionsToObject(exc:String):Object {
			var w:Array = exc.split(', ');
			var r:Object = new Object();
			var key:String;
			for (var i:int = 0; i < w.length; i++) {
				key = w[i].replace(/-/g, '');
				if (!r.hasOwnProperty(key)) {
					r[key] = w[i];
				}
			}
			return r;
		}
		
		private function hyphenateURL(url:String):String {
			return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen);
		}
		
		private function hyphenateWord(lang:String, word:String):String{
			var lo:Object = languages[lang];
			var parts:Array;
			var i:int;
			var w:String;
			var wl:int;
			var s:Array;
			var hypos:Array;
			var p:int;
			var maxwins:int;
			var win:int;
			var pat:String;
			var patk:String;
			var patl:int;
			var c:String;
			var digits:int;
			var z:int;
			var numb3rs:Object;
			var n:int;
			var inserted:int;
			var hyphenatedword:String;
			if(word == ''){
				return '';
			}
			if(enableCache && lo.cache.hasOwnProperty(word)){
				//the word is in the cache
				return lo.cache[word];
			}
			if(lo.exceptions.hasOwnProperty(word)){
				//the word is in the exceptions list
				return lo.exceptions[word].replace(new RegExp(/-/g), hyphen);
			}
			if(word.indexOf('-') !== -1){
				//word contains '-' -> hyphenate the parts separated with '-'
				parts = word.split('-');
				for(i = 0; i < parts.length; i++){
					parts[i] = hyphenateWord(lang, parts[i]);
				}
				return parts.join(softHyphen);
			}
			//finally the core hyphenation algorithm
			w = '_' + word + '_';
			wl = w.length;
			s = w.split('');
			w = w.toLowerCase();
			hypos = new Array();
			numb3rs = {'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true};	n = wl - lo.shortestPattern;
			n = wl - lo.shortestPattern;
			for(p = 0; p <= n; p++){
				maxwins = Math.min((wl - p), lo.longestPattern);
				for(win = lo.shortestPattern; win <= maxwins; win++){
					if(lo.patterns.hasOwnProperty(patk = w.substr(p, win))){
						pat = lo.patterns[patk];
					} else{
						continue;
					}
					digits = 1;
					patl = pat.length;
					for(i = 0; i < patl; i++){
						c = pat.charAt(i);
						if(numb3rs[c]){
							if(i === 0){
								z = p - 1;
								if(!hypos[z] || hypos[z] < c){
									hypos[z] = c;
								}
							} else{
								z = p + i - digits;
								if(!hypos[z] || hypos[z] < c){
									hypos[z] = c;
								}
							}
							digits++;
						}
					}
				}
			}
			inserted = 0;
			for(i = lo.leftmin; i <= (word.length - lo.rightmin); i++){
				if((hypos[i] & 1)){
					s.splice(i + inserted + 1, 0, hyphen);
					inserted++;
				}
			}
			hyphenatedword = s.slice(1, -1).join('');
			if(enableCache){
				lo.cache[word] = hyphenatedword;
			}
			return hyphenatedword;
		}

	}

}
internal class SingletonEnforcer{}